﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing.Design;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Windows.Forms.Design.Behavior;
using Vanara.Extensions;

namespace Vanara.Windows.Forms.Design
{
	/// <summary>Interface for an action that has items and a category.</summary>
	public interface IActionGetItem
	{
		/// <summary>Gets the category.</summary>
		/// <value>The category.</value>
		string Category { get; }

		/// <summary>Gets the item.</summary>
		/// <param name="actions">The actions.</param>
		/// <param name="mbr">The MBR.</param>
		/// <returns></returns>
		DesignerActionItem GetItem(DesignerActionList actions, global::System.Reflection.MemberInfo mbr);
	}

	/// <summary>Methods to assist when using designer code.</summary>
	public static class ComponentDesignerExtension
	{
		/// <summary>Launches the design-time editor for the property of the component behind a designer.</summary>
		/// <param name="designer">The designer for a component.</param>
		/// <param name="propName">The name of the property to edit. If this value is null, the default property for the object is used.</param>
		/// <param name="objectToChange">
		/// The object on which to edit the property. If this value is null, the Component property of the designer is used.
		/// </param>
		/// <returns>The new value returned by the editor.</returns>
		public static object EditValue(this ComponentDesigner designer, string propName, object objectToChange = null)
		{
			if (objectToChange == null) objectToChange = designer.Component;
			var prop = (propName == null) ? TypeDescriptor.GetDefaultProperty(objectToChange) : TypeDescriptor.GetProperties(objectToChange)[propName];
			if (prop == null) throw new ArgumentException("Unable to retrieve specified property.");
			var context = new EditorServiceContext(designer, prop);
			var editor = prop.GetEditor(typeof(UITypeEditor)) as UITypeEditor;
			var curVal = prop.GetValue(objectToChange);
			var newVal = editor?.EditValue(context, context, curVal);
			if (newVal != curVal)
				try { prop.SetValue(objectToChange, newVal); }
				catch (CheckoutException) { }
			return newVal;
		}

		/// <summary>Sets a property on the component behind a designer.</summary>
		/// <typeparam name="T">The type of the property value.</typeparam>
		/// <param name="d">The designer for a component.</param>
		/// <param name="propName">
		/// The name of the property to set. If this value is null, the default property for the object is used. This method will not set
		/// the property if the property type does not match <typeparamref name="T"/>, if the property is read-only, or if the property is
		/// not browsable.
		/// </param>
		/// <param name="value">The value to assign to the property.</param>
		public static void SetComponentProperty<T>(this ComponentDesigner d, string propName, T value)
		{
			var propDesc = (propName == null) ? TypeDescriptor.GetDefaultProperty(d.Component) : TypeDescriptor.GetProperties(d.Component)[propName];
			if (propDesc != null && propDesc.PropertyType == typeof(T) && !propDesc.IsReadOnly && propDesc.IsBrowsable)
				propDesc.SetValue(d.Component, value);
		}

		/// <summary>Shows a form tied to a designer.</summary>
		/// <param name="designer">The designer for a component.</param>
		/// <param name="dialog">A form instance.</param>
		/// <returns>The result of calling ShowDialog on the form.</returns>
		public static DialogResult ShowDialog(this ComponentDesigner designer, Form dialog)
		{
			var context = new EditorServiceContext(designer);
			return context.ShowDialog(dialog);
		}
	}

	/// <summary>Extension methods for IServiceProvider.</summary>
	public static partial class ServiceProviderExtension
	{
		/// <summary>Gets the service.</summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="sp">The sp.</param>
		/// <returns></returns>
		public static T GetService<T>(this IServiceProvider sp) where T : class => (T)sp.GetService(typeof(T));
	}

	/// <summary>A designer for components that support attributes.</summary>
	/// <typeparam name="TComponent">The type of the component.</typeparam>
	/// <seealso cref="System.ComponentModel.Design.ComponentDesigner"/>
	public abstract class AttributedComponentDesigner<TComponent> : ComponentDesigner where TComponent : Component
	{
		private IDictionary<string, List<Attribute>> redirectedProps;
		private DesignerVerbCollection verbs;

		/// <summary>Initializes a new instance of the <see cref="AttributedComponentDesigner{TComponent}"/> class.</summary>
		public AttributedComponentDesigner()
		{
			redirectedProps = this.GetRedirectedProperties();
			verbs = this.GetAttributedVerbs();
		}

		/// <summary>Gets the design-time verbs supported by the component that is associated with the designer.</summary>
		public override DesignerVerbCollection Verbs => verbs;

		/// <summary>Gets the design-time action lists supported by the component associated with the designer.</summary>
		public override DesignerActionListCollection ActionLists =>
			this.GetActionLists(Component, Actions, base.ActionLists, Verbs);

		/// <summary>Gets the component this designer is designing.</summary>
		public new TComponent Component => (TComponent)base.Component;

		/// <summary>Gets the actions.</summary>
		/// <value>The actions.</value>
		protected virtual AttributedDesignerActionList Actions => null;

		/// <summary>Gets the properties to remove.</summary>
		/// <value>The properties to remove.</value>
		protected virtual IEnumerable<string> PropertiesToRemove => null;

		/// <summary>Allows a designer to add to the set of properties that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.</summary>
		/// <param name="properties">The properties for the class of the component.</param>
		protected override void PreFilterProperties(IDictionary properties)
		{
			base.PreFilterProperties(properties);

			if (redirectedProps != null)
				this.RedirectRegisteredProperties(properties, redirectedProps);

			if (PropertiesToRemove != null)
				this.RemoveItems(properties, PropertiesToRemove);
		}
	}

	/// <summary>An extended designer for components that support attributes.</summary>
	/// <typeparam name="TComponent">The type of the component.</typeparam>
	/// <seealso cref="System.ComponentModel.Design.ComponentDesigner"/>
	public abstract class AttributedComponentDesignerEx<TComponent> : AttributedComponentDesigner<TComponent>
		where TComponent : Component
	{
		private Adorner adorner;

		/// <summary>Gets the behavior service.</summary>
		/// <value>The behavior service.</value>
		public BehaviorService BehaviorService { get; private set; }

		/// <summary>Gets the component change service.</summary>
		/// <value>The component change service.</value>
		public IComponentChangeService ComponentChangeService { get; private set; }

		/// <summary>Gets the selection service.</summary>
		/// <value>The selection service.</value>
		public ISelectionService SelectionService { get; private set; }

		/// <summary>Gets the glyphs.</summary>
		/// <value>The glyphs.</value>
		public virtual GlyphCollection Glyphs => Adorner.Glyphs;

		internal Adorner Adorner
		{
			get
			{
				if (adorner == null)
					BehaviorService.Adorners.Add(adorner = new Adorner());
				return adorner;
			}
		}

		/// <summary>Prepares the designer to view, edit, and design the specified component.</summary>
		/// <param name="component">The component for this designer.</param>
		public override void Initialize(IComponent component)
		{
			base.Initialize(component);
			BehaviorService = GetService<BehaviorService>();
			SelectionService = GetService<ISelectionService>();
			if (SelectionService != null)
				SelectionService.SelectionChanged += OnSelectionChanged;
			ComponentChangeService = GetService<IComponentChangeService>();
			if (ComponentChangeService != null)
				ComponentChangeService.ComponentChanged += OnComponentChanged;
		}

		/// <summary>
		/// Releases the unmanaged resources used by the <see cref="T:System.ComponentModel.Design.ComponentDesigner"/> and optionally
		/// releases the managed resources.
		/// </summary>
		/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (BehaviorService != null & adorner != null)
					BehaviorService.Adorners.Remove(adorner);
				var ss = SelectionService;
				if (ss != null)
					ss.SelectionChanged -= OnSelectionChanged;
				var cs = ComponentChangeService;
				if (cs != null)
					cs.ComponentChanged -= OnComponentChanged;
			}
			base.Dispose(disposing);
		}

		/// <summary>Gets the service.</summary>
		/// <typeparam name="TS">The type of the s.</typeparam>
		/// <returns></returns>
		protected virtual TS GetService<TS>() where TS : class => (TS)GetService(typeof(TS));

		/// <summary>Called when [component changed].</summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="ComponentChangedEventArgs"/> instance containing the event data.</param>
		protected virtual void OnComponentChanged(object sender, ComponentChangedEventArgs e)
		{
		}

		/// <summary>Called when [selection changed].</summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
		protected virtual void OnSelectionChanged(object sender, EventArgs e)
		{
		}
	}

	/// <summary>A designer for controls that support attributes.</summary>
	/// <typeparam name="TControl">The type of the control.</typeparam>
	/// <seealso cref="System.Windows.Forms.Design.ControlDesigner"/>
	public abstract class AttributedControlDesigner<TControl> : ControlDesigner where TControl : Control
	{
		private IDictionary<string, List<Attribute>> redirectedEvents;
		private IDictionary<string, List<Attribute>> redirectedProps;
		private DesignerVerbCollection verbs;

		/// <summary>Initializes a new instance of the <see cref="AttributedControlDesigner{TControl}"/> class.</summary>
		public AttributedControlDesigner()
		{
			redirectedEvents = this.GetRedirectedEvents();
			redirectedProps = this.GetRedirectedProperties();
			verbs = this.GetAttributedVerbs();
		}

		/// <summary>Gets the design-time verbs supported by the component that is associated with the designer.</summary>
		public override DesignerVerbCollection Verbs => verbs;

		/// <summary>Gets the design-time action lists supported by the component associated with the designer.</summary>
		public override DesignerActionListCollection ActionLists =>
			this.GetActionLists(Control, Actions, base.ActionLists, Verbs);

		/// <summary>Gets the control that the designer is designing.</summary>
		public new TControl Control => (TControl)base.Control;

		/// <summary>Gets the actions.</summary>
		/// <value>The actions.</value>
		protected virtual AttributedDesignerActionList Actions => null;

		/// <summary>Gets the events to remove.</summary>
		/// <value>The events to remove.</value>
		protected virtual IEnumerable<string> EventsToRemove => null;

		/// <summary>Gets the properties to remove.</summary>
		/// <value>The properties to remove.</value>
		protected virtual IEnumerable<string> PropertiesToRemove => null;

		/// <summary>Allows a designer to add to the set of events that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.</summary>
		/// <param name="events">The events for the class of the component.</param>
		protected override void PreFilterEvents(IDictionary events)
		{
			base.PreFilterEvents(events);

			if (redirectedEvents != null)
				this.RedirectRegisteredEvents(events, redirectedEvents);

			if (EventsToRemove != null)
				this.RemoveItems(events, EventsToRemove);
		}

		/// <summary>Adjusts the set of properties the component exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.</summary>
		/// <param name="properties">An <see cref="T:System.Collections.IDictionary"/> containing the properties for the class of the component.</param>
		protected override void PreFilterProperties(IDictionary properties)
		{
			base.PreFilterProperties(properties);

			if (redirectedProps != null)
				this.RedirectRegisteredProperties(properties, redirectedProps);

			if (PropertiesToRemove != null)
				this.RemoveItems(properties, PropertiesToRemove);
		}
	}

	/// <summary>An extended designer for controls that support attributes.</summary>
	/// <typeparam name="TControl">The type of the control.</typeparam>
	/// <seealso cref="Vanara.Windows.Forms.Design.AttributedControlDesigner{TControl}"/>
	public abstract class AttributedControlDesignerEx<TControl> : AttributedControlDesigner<TControl> where TControl : Control
	{
		private Adorner adorner;

		/// <summary>Gets the component change service.</summary>
		/// <value>The component change service.</value>
		public IComponentChangeService ComponentChangeService { get; private set; }

		/// <summary>Gets the selection service.</summary>
		/// <value>The selection service.</value>
		public ISelectionService SelectionService { get; private set; }

		/// <summary>Gets the <see cref="T:System.Windows.Forms.Design.Behavior.BehaviorService"/> from the design environment.</summary>
		public new BehaviorService BehaviorService => base.BehaviorService;

		/// <summary>Gets the glyphs.</summary>
		/// <value>The glyphs.</value>
		public virtual GlyphCollection Glyphs => Adorner.Glyphs;

		internal Adorner Adorner
		{
			get
			{
				if (adorner == null)
					BehaviorService.Adorners.Add(adorner = new Adorner());
				return adorner;
			}
		}

		/// <summary>Initializes the designer with the specified component.</summary>
		/// <param name="component">
		/// The <see cref="T:System.ComponentModel.IComponent"/> to associate the designer with. This component must always be an instance
		/// of, or derive from, <see cref="T:System.Windows.Forms.Control"/>.
		/// </param>
		public override void Initialize(IComponent component)
		{
			base.Initialize(component);
			SelectionService = GetService<ISelectionService>();
			if (SelectionService != null)
				SelectionService.SelectionChanged += OnSelectionChanged;
			ComponentChangeService = GetService<IComponentChangeService>();
			if (ComponentChangeService != null)
				ComponentChangeService.ComponentChanged += OnComponentChanged;
		}

		/// <summary>
		/// Releases the unmanaged resources used by the <see cref="T:System.Windows.Forms.Design.ControlDesigner"/> and optionally releases
		/// the managed resources.
		/// </summary>
		/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				BehaviorService?.Adorners.Remove(adorner);
				var ss = SelectionService;
				if (ss != null)
					ss.SelectionChanged -= OnSelectionChanged;
				var cs = ComponentChangeService;
				if (cs != null)
					cs.ComponentChanged -= OnComponentChanged;
			}
			base.Dispose(disposing);
		}

		/// <summary>Gets the service.</summary>
		/// <typeparam name="TS">The type of the s.</typeparam>
		/// <returns></returns>
		protected virtual TS GetService<TS>() where TS : class => (TS)GetService(typeof(TS));

		/// <summary>Called when a component has changed.</summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="ComponentChangedEventArgs"/> instance containing the event data.</param>
		protected virtual void OnComponentChanged(object sender, ComponentChangedEventArgs e)
		{
		}

		/// <summary>Called when the selection on the designer has changed.</summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
		protected virtual void OnSelectionChanged(object sender, EventArgs e)
		{
		}
	}

	/// <summary>A designer action list pulled from attributes.</summary>
	/// <seealso cref="System.ComponentModel.Design.DesignerActionList"/>
	public abstract class AttributedDesignerActionList : DesignerActionList
	{
		private IEnumerable<DesignerActionItem> fullAIList;

		/// <summary>Initializes a new instance of the <see cref="AttributedDesignerActionList"/> class.</summary>
		/// <param name="designer">The designer.</param>
		/// <param name="component">The component.</param>
		protected AttributedDesignerActionList(ComponentDesigner designer, IComponent component)
			: base(component)
		{
			ParentDesigner = designer;
			AutoShow = true;
		}

		/// <summary>Gets the parent designer.</summary>
		/// <value>The parent designer.</value>
		public ComponentDesigner ParentDesigner { get; }

		/// <summary>
		/// Returns the collection of <see cref="T:System.ComponentModel.Design.DesignerActionItem"/> objects contained in the list.
		/// </summary>
		/// <returns>A <see cref="T:System.ComponentModel.Design.DesignerActionItem"/> array that contains the items in this list.</returns>
		public override DesignerActionItemCollection GetSortedActionItems()
		{
			// Retrieve all attributed methods and properties
			if (fullAIList == null)
				fullAIList = this.GetAllAttributedActionItems();

			// Filter for conditions and load
			return this.GetFilteredActionItems(fullAIList);
		}

		/// <summary>Gets the component property.</summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="propName">Name of the property.</param>
		/// <returns></returns>
		protected T GetComponentProperty<T>(string propName)
		{
			var p = ComponentProp(propName, typeof(T));
			if (p != null)
				return (T)p.GetValue(Component, null);
			return default;
		}

		/// <summary>Sets the component property.</summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="propName">Name of the property.</param>
		/// <param name="value">The value.</param>
		protected void SetComponentProperty<T>(string propName, T value)
		{
			ComponentProp(propName, typeof(T))?.SetValue(Component, value, null);
		}

		private global::System.Reflection.PropertyInfo ComponentProp(string propName, Type retType) => Component.GetType().GetProperty(propName, InternalComponentDesignerExtension.allInstBind, null, retType, Type.EmptyTypes, null);
	}

	/// <summary>A designer for parent controls supported by attributes.</summary>
	/// <typeparam name="TControl">The type of the control.</typeparam>
	/// <seealso cref="System.Windows.Forms.Design.ParentControlDesigner"/>
	public abstract class AttributedParentControlDesigner<TControl> : ParentControlDesigner where TControl : Control
	{
		private IDictionary<string, List<Attribute>> redirectedProps;
		private DesignerVerbCollection verbs;

		/// <summary>Initializes a new instance of the <see cref="AttributedParentControlDesigner{TControl}"/> class.</summary>
		public AttributedParentControlDesigner()
		{
			redirectedProps = this.GetRedirectedProperties();
			verbs = this.GetAttributedVerbs();
		}

		/// <summary>Gets the design-time verbs supported by the component that is associated with the designer.</summary>
		public override DesignerVerbCollection Verbs => verbs;

		/// <summary>Gets the design-time action lists supported by the component associated with the designer.</summary>
		public override DesignerActionListCollection ActionLists =>
			this.GetActionLists(Control, Actions, base.ActionLists, Verbs);

		/// <summary>Gets the control that the designer is designing.</summary>
		public new TControl Control => (TControl)base.Control;

		/// <summary>Gets the actions.</summary>
		/// <value>The actions.</value>
		protected virtual AttributedDesignerActionList Actions => null;

		/// <summary>Gets the properties to remove.</summary>
		/// <value>The properties to remove.</value>
		protected virtual IEnumerable<string> PropertiesToRemove => null;

		/// <summary>Adjusts the set of properties the component will expose through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.</summary>
		/// <param name="properties">
		/// An <see cref="T:System.Collections.IDictionary"/> that contains the properties for the class of the component.
		/// </param>
		protected override void PreFilterProperties(IDictionary properties)
		{
			base.PreFilterProperties(properties);

			if (redirectedProps != null)
				this.RedirectRegisteredProperties(properties, redirectedProps);

			if (PropertiesToRemove != null)
				this.RemoveItems(properties, PropertiesToRemove);
		}
	}

	/// <summary>An extended designer for parent controls supported by attributes.</summary>
	/// <typeparam name="TControl">The type of the control.</typeparam>
	/// <seealso cref="Vanara.Windows.Forms.Design.AttributedParentControlDesigner{TControl}"/>
	public abstract class AttributedParentControlDesignerEx<TControl> : AttributedParentControlDesigner<TControl> where TControl : Control
	{
		private Adorner adorner;

		/// <summary>Gets the component change service.</summary>
		/// <value>The component change service.</value>
		public IComponentChangeService ComponentChangeService { get; private set; }

		/// <summary>Gets the selection service.</summary>
		/// <value>The selection service.</value>
		public ISelectionService SelectionService { get; private set; }

		/// <summary>Gets the <see cref="T:System.Windows.Forms.Design.Behavior.BehaviorService"/> from the design environment.</summary>
		public new BehaviorService BehaviorService => base.BehaviorService;

		/// <summary>Gets the glyphs.</summary>
		/// <value>The glyphs.</value>
		public virtual GlyphCollection Glyphs => Adorner.Glyphs;

		internal Adorner Adorner
		{
			get
			{
				if (adorner == null)
					BehaviorService.Adorners.Add(adorner = new Adorner());
				return adorner;
			}
		}

		/// <summary>Initializes the designer with the specified component.</summary>
		/// <param name="component">The <see cref="T:System.ComponentModel.IComponent"/> to associate with the designer.</param>
		public override void Initialize(IComponent component)
		{
			base.Initialize(component);
			SelectionService = GetService<ISelectionService>();
			if (SelectionService != null)
				SelectionService.SelectionChanged += OnSelectionChanged;
			ComponentChangeService = GetService<IComponentChangeService>();
			if (ComponentChangeService != null)
				ComponentChangeService.ComponentChanged += OnComponentChanged;
		}

		/// <summary>
		/// Releases the unmanaged resources used by the <see cref="T:System.Windows.Forms.Design.ParentControlDesigner"/>, and optionally
		/// releases the managed resources.
		/// </summary>
		/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (BehaviorService != null & adorner != null)
					BehaviorService.Adorners.Remove(adorner);
				var ss = SelectionService;
				if (ss != null)
					ss.SelectionChanged -= OnSelectionChanged;
				var cs = ComponentChangeService;
				if (cs != null)
					cs.ComponentChanged -= OnComponentChanged;
			}
			base.Dispose(disposing);
		}

		/// <summary>Gets the service.</summary>
		/// <typeparam name="TS">The type of the s.</typeparam>
		/// <returns></returns>
		protected virtual TS GetService<TS>() where TS : class => (TS)GetService(typeof(TS));

		/// <summary>Called when [component changed].</summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="ComponentChangedEventArgs"/> instance containing the event data.</param>
		protected virtual void OnComponentChanged(object sender, ComponentChangedEventArgs e)
		{
		}

		/// <summary>Called when [selection changed].</summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
		protected virtual void OnSelectionChanged(object sender, EventArgs e)
		{
		}
	}

	/// <summary>Attribute placed on methods that indicate they support a designer action.</summary>
	/// <seealso cref="System.Attribute"/>
	/// <seealso cref="Vanara.Windows.Forms.Design.IActionGetItem"/>
	[AttributeUsage(AttributeTargets.Method)]
	public sealed class DesignerActionMethodAttribute : Attribute, IActionGetItem
	{
		/// <summary>Initializes a new instance of the <see cref="DesignerActionMethodAttribute"/> class.</summary>
		/// <param name="displayName">The display name.</param>
		/// <param name="displayOrder">The display order.</param>
		public DesignerActionMethodAttribute(string displayName, int displayOrder = 0)
		{
			DisplayName = displayName;
			DisplayOrder = displayOrder;
		}

		/// <summary>Gets or sets a value indicating whether [allow associate].</summary>
		/// <value><see langword="true"/> if [allow associate]; otherwise, <see langword="false"/>.</value>
		public bool AllowAssociate { get; set; }

		/// <summary>Gets or sets the category.</summary>
		/// <value>The category.</value>
		public string Category { get; set; }

		/// <summary>Gets or sets the condition.</summary>
		/// <value>The condition.</value>
		public string Condition { get; set; }

		/// <summary>Gets or sets the description.</summary>
		/// <value>The description.</value>
		public string Description { get; set; }

		/// <summary>Gets the display name.</summary>
		/// <value>The display name.</value>
		public string DisplayName { get; }

		/// <summary>Gets the display order.</summary>
		/// <value>The display order.</value>
		public int DisplayOrder { get; }

		/// <summary>Gets or sets a value indicating whether [include as designer verb].</summary>
		/// <value><see langword="true"/> if [include as designer verb]; otherwise, <see langword="false"/>.</value>
		public bool IncludeAsDesignerVerb { get; set; }

		DesignerActionItem IActionGetItem.GetItem(DesignerActionList actions, global::System.Reflection.MemberInfo mbr)
		{
			var ret = new DesignerActionMethodItem(actions, mbr.Name, DisplayName, Category, Description, IncludeAsDesignerVerb)
			{ AllowAssociate = AllowAssociate };
			if (!string.IsNullOrEmpty(Condition))
				ret.Properties.Add("Condition", Condition);
			ret.Properties.Add("Order", DisplayOrder);
			return ret;
		}
	}

	/// <summary>Attribute placed on properties that indicate they support a designer action.</summary>
	/// <seealso cref="System.Attribute"/>
	/// <seealso cref="Vanara.Windows.Forms.Design.IActionGetItem"/>
	[AttributeUsage(AttributeTargets.Property)]
	public sealed class DesignerActionPropertyAttribute : Attribute, IActionGetItem
	{
		/// <summary>Initializes a new instance of the <see cref="DesignerActionPropertyAttribute"/> class.</summary>
		/// <param name="displayName">The display name.</param>
		/// <param name="displayOrder">The display order.</param>
		public DesignerActionPropertyAttribute(string displayName, int displayOrder = 0)
		{
			DisplayName = displayName;
			DisplayOrder = displayOrder;
		}

		/// <summary>Gets or sets a value indicating whether [allow associate].</summary>
		/// <value><see langword="true"/> if [allow associate]; otherwise, <see langword="false"/>.</value>
		public bool AllowAssociate { get; set; }

		/// <summary>Gets or sets the category.</summary>
		/// <value>The category.</value>
		public string Category { get; set; }

		/// <summary>Gets or sets the condition.</summary>
		/// <value>The condition.</value>
		public string Condition { get; set; }

		/// <summary>Gets or sets the description.</summary>
		/// <value>The description.</value>
		public string Description { get; set; }

		/// <summary>Gets the display name.</summary>
		/// <value>The display name.</value>
		public string DisplayName { get; }

		/// <summary>Gets the display order.</summary>
		/// <value>The display order.</value>
		public int DisplayOrder { get; }

		DesignerActionItem IActionGetItem.GetItem(DesignerActionList actions, global::System.Reflection.MemberInfo mbr)
		{
			var ret = new DesignerActionPropertyItem(mbr.Name, DisplayName, Category, Description)
			{ AllowAssociate = AllowAssociate };
			if (!string.IsNullOrEmpty(Condition))
				ret.Properties.Add("Condition", Condition);
			ret.Properties.Add("Order", DisplayOrder);
			return ret;
		}
	}

	/// <summary>Attribute placed on methods that indicate they support a designer attribute.</summary>
	/// <seealso cref="System.Attribute"/>
	[AttributeUsage(AttributeTargets.Method)]
	public sealed class DesignerVerbAttribute : Attribute
	{
		private readonly CommandID cmdId;
		private readonly string menuText;

		/// <summary>Initializes a new instance of the <see cref="DesignerVerbAttribute"/> class.</summary>
		/// <param name="menuText">The menu text.</param>
		public DesignerVerbAttribute(string menuText)
		{
			this.menuText = menuText;
		}

		/// <summary>Initializes a new instance of the <see cref="DesignerVerbAttribute"/> class.</summary>
		/// <param name="menuText">The menu text.</param>
		/// <param name="commandMenuGroup">The command menu group.</param>
		/// <param name="commandId">The command identifier.</param>
		public DesignerVerbAttribute(string menuText, Guid commandMenuGroup, int commandId)
		{
			this.menuText = menuText;
			cmdId = new CommandID(commandMenuGroup, commandId);
		}

		internal DesignerVerb GetDesignerVerb(object obj, global::System.Reflection.MethodInfo mi)
		{
			var handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), obj, mi);
			if (cmdId != null)
				return new DesignerVerb(menuText, handler, cmdId);
			return new DesignerVerb(menuText, handler);
		}
	}

	/// <summary>A service context implementation for an editor.</summary>
	/// <seealso cref="System.Windows.Forms.Design.IWindowsFormsEditorService"/>
	/// <seealso cref="System.ComponentModel.ITypeDescriptorContext"/>
	public class EditorServiceContext : IWindowsFormsEditorService, ITypeDescriptorContext
	{
		private readonly ComponentDesigner designer;
		private readonly PropertyDescriptor targetProperty;
		private IComponentChangeService componentChangeSvc;

		internal EditorServiceContext(ComponentDesigner designer)
		{
			this.designer = designer;
		}

		internal EditorServiceContext(ComponentDesigner designer, PropertyDescriptor prop)
		{
			this.designer = designer;
			targetProperty = prop;
			if (prop == null)
			{
				prop = TypeDescriptor.GetDefaultProperty(designer.Component);
				if ((prop != null) && typeof(ICollection).IsAssignableFrom(prop.PropertyType))
					targetProperty = prop;
			}
		}

		internal EditorServiceContext(ComponentDesigner designer, PropertyDescriptor prop, string newVerbText)
			: this(designer, prop)
		{
			this.designer.Verbs.Add(new DesignerVerb(newVerbText, OnEditItems));
		}

		IContainer ITypeDescriptorContext.Container => designer.Component.Site?.Container;

		object ITypeDescriptorContext.Instance => designer.Component;

		PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor => targetProperty;

		private IComponentChangeService ChangeService => componentChangeSvc ?? (componentChangeSvc = GetService<IComponentChangeService>());

		/// <summary>Shows the specified <see cref="T:System.Windows.Forms.Form"/>.</summary>
		/// <param name="dialog">The <see cref="T:System.Windows.Forms.Form"/> to display.</param>
		/// <returns>A <see cref="T:System.Windows.Forms.DialogResult"/> indicating the result code returned by the <see cref="T:System.Windows.Forms.Form"/>.</returns>
		public DialogResult ShowDialog(Form dialog)
		{
			var service = GetService<IUIService>();
			if (service != null)
				return service.ShowDialog(dialog);
			return dialog.ShowDialog(designer.Component as IWin32Window);
		}

		void IWindowsFormsEditorService.CloseDropDown()
		{
		}

		void IWindowsFormsEditorService.DropDownControl(Control control)
		{
		}

		object IServiceProvider.GetService(Type serviceType)
		{
			if ((serviceType == typeof(ITypeDescriptorContext)) || (serviceType == typeof(IWindowsFormsEditorService)))
				return this;
			return designer.Component?.Site?.GetService(serviceType);
		}

		void ITypeDescriptorContext.OnComponentChanged()
		{
			ChangeService.OnComponentChanged(designer.Component, targetProperty, null, null);
		}

		bool ITypeDescriptorContext.OnComponentChanging()
		{
			try
			{
				ChangeService.OnComponentChanging(designer.Component, targetProperty);
			}
			catch (CheckoutException exception)
			{
				if (exception != CheckoutException.Canceled)
					throw;
				return false;
			}
			return true;
		}

		private T GetService<T>() where T : class => ((IServiceProvider)this).GetService<T>();

		private void OnEditItems(object sender, EventArgs e)
		{
			var component = targetProperty.GetValue(designer.Component);
			if (component != null)
			{
				var editor = TypeDescriptor.GetEditor(component, typeof(UITypeEditor)) as CollectionEditor;
				editor?.EditValue(this, this, component);
			}
		}
	}

	/// <summary>Attribute placed on class items that indicate they support a designer redirected item.</summary>
	/// <seealso cref="System.Attribute"/>
	[AttributeUsage(AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Method)]
	public sealed class RedirectedDesignerItemAttribute : Attribute
	{
		/// <summary>Initializes a new instance of the <see cref="RedirectedDesignerItemAttribute"/> class.</summary>
		public RedirectedDesignerItemAttribute() { ApplyOtherAttributes = true; }

		/// <summary>Gets or sets a value indicating whether to apply other attributes.</summary>
		/// <value><see langword="true"/> if this should apply other attributes; otherwise, <see langword="false"/>.</value>
		public bool ApplyOtherAttributes { get; set; }
	}

	/// <summary>A behavior derivative for a supplied type.</summary>
	/// <typeparam name="TControlDesigner">The type of the control designer.</typeparam>
	/// <seealso cref="System.Windows.Forms.Design.Behavior.Behavior"/>
	public abstract class TypedBehavior<TControlDesigner> : Behavior where TControlDesigner : ControlDesigner
	{
		/// <summary>Initializes a new instance of the <see cref="TypedBehavior{TControlDesigner}"/> class.</summary>
		/// <param name="designer">The designer.</param>
		protected TypedBehavior(TControlDesigner designer)
		{
			Designer = designer;
		}

		/// <summary>Gets the designer.</summary>
		/// <value>The designer.</value>
		public TControlDesigner Designer { get; }
	}

	/// <summary>An action list for a generic designer.</summary>
	/// <typeparam name="TComponentDesigner">The type of the component designer.</typeparam>
	/// <typeparam name="TComponent">The type of the component.</typeparam>
	/// <seealso cref="Vanara.Windows.Forms.Design.AttributedDesignerActionList"/>
	public abstract class TypedDesignerActionList<TComponentDesigner, TComponent> : AttributedDesignerActionList where TComponentDesigner : ComponentDesigner where TComponent : Component
	{
		/// <summary>Initializes a new instance of the <see cref="TypedDesignerActionList{TComponentDesigner, TComponent}"/> class.</summary>
		/// <param name="designer">The designer.</param>
		/// <param name="component">The component.</param>
		protected TypedDesignerActionList(TComponentDesigner designer, TComponent component) : base(designer, component)
		{
			ParentDesigner = designer;
		}

		/// <summary>Gets the parent designer.</summary>
		/// <value>The parent designer.</value>
		public new TComponentDesigner ParentDesigner { get; }

		/// <summary>Gets the component related to <see cref="T:System.ComponentModel.Design.DesignerActionList"/>.</summary>
		public new TComponent Component => (TComponent)base.Component;
	}

	/// <summary>A glyph associated with a designer.</summary>
	/// <typeparam name="TControlDesigner">The type of the control designer.</typeparam>
	/// <seealso cref="System.Windows.Forms.Design.Behavior.Glyph"/>
	/// <seealso cref="System.IDisposable"/>
	public abstract class TypedGlyph<TControlDesigner> : Glyph, IDisposable where TControlDesigner : ControlDesigner
	{
		/// <summary>Initializes a new instance of the <see cref="TypedGlyph{TControlDesigner}"/> class.</summary>
		/// <param name="designer">The designer.</param>
		/// <param name="behavior">The behavior.</param>
		protected TypedGlyph(TControlDesigner designer, Behavior behavior) : base(behavior)
		{
			Designer = designer;
		}

		/// <summary>Gets the designer.</summary>
		/// <value>The designer.</value>
		public TControlDesigner Designer { get; }

		/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
		public virtual void Dispose() { }

		/// <summary>Sets the behavior.</summary>
		/// <param name="b">The b.</param>
		public void SetBehavior(TypedBehavior<TControlDesigner> b) { base.SetBehavior(b); }
	}

	internal static class InternalComponentDesignerExtension
	{
		public const BindingFlags allInstBind = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;

		public static DesignerActionListCollection GetActionLists(this ComponentDesigner designer, Component component, AttributedDesignerActionList actions, DesignerActionListCollection baseActions, DesignerVerbCollection verbs)
		{
			var lists = new DesignerActionListCollection();
			if (baseActions != null && baseActions.Count > 0)
				lists.AddRange(baseActions);
			if (actions != null)
				lists.Add(actions);
			if (verbs != null && verbs.Count > 0)
				lists.Add(new DesignerActionVerbList(verbs));
			return lists;
		}

		public static IEnumerable<DesignerActionItem> GetAllAttributedActionItems(this DesignerActionList actionList)
		{
			var fullAIList = new List<DesignerActionItem>();
			foreach (var mbr in actionList.GetType().GetMethods(allInstBind))
			{
				foreach (IActionGetItem attr in mbr.GetCustomAttributes(typeof(DesignerActionMethodAttribute), true))
				{
					if (mbr.ReturnType == typeof(void) && mbr.GetParameters().Length == 0)
						fullAIList.Add(attr.GetItem(actionList, mbr));
					else
						throw new FormatException("DesignerActionMethodAttribute must be applied to a method returning void and having no parameters.");
				}
			}
			foreach (var mbr in actionList.GetType().GetProperties(allInstBind))
			{
				if (mbr.GetIndexParameters().Length > 0)
					throw new FormatException("DesignerActionPropertyAttribute must be applied to non-indexed properties.");
				foreach (IActionGetItem attr in mbr.GetCustomAttributes(typeof(DesignerActionPropertyAttribute), true))
					fullAIList.Add(attr.GetItem(actionList, mbr));
			}
			fullAIList.Sort(CompareItems);
			return fullAIList;

			int CompareItems(DesignerActionItem a, DesignerActionItem b)
			{
				var c = string.Compare(a?.Category ?? string.Empty, b?.Category ?? string.Empty, true);
				if (c != 0)
					return c;
				c = (int)(a?.Properties["Order"] ?? 0) - (int)(b?.Properties["Order"] ?? 0);
				if (c != 0)
					return c;
				return string.Compare(a?.DisplayName, b?.DisplayName, true);
			}
		}

		public static DesignerVerbCollection GetAttributedVerbs(this ComponentDesigner designer)
		{
			var verbs = new DesignerVerbCollection();
			foreach (var m in designer.GetType().GetMethods(allInstBind))
			{
				foreach (DesignerVerbAttribute attr in m.GetCustomAttributes(typeof(DesignerVerbAttribute), true))
				{
					verbs.Add(attr.GetDesignerVerb(designer, m));
				}
			}
			return verbs.Count > 0 ? verbs : null;
		}

		public static DesignerActionItemCollection GetFilteredActionItems(this DesignerActionList actionList, IEnumerable<DesignerActionItem> fullAIList)
		{
			var col = new DesignerActionItemCollection();
			foreach (var ai in fullAIList)
				if (CheckCondition(ai))
					col.Add(ai);

			// Add header items for displayed items
			string cat = null;
			for (var i = 0; i < col.Count; i++)
			{
				var curCat = col[i].Category;
				if (string.Compare(curCat, cat, true) != 0)
				{
					col.Insert(i++, new DesignerActionHeaderItem(curCat));
					cat = curCat;
				}
			}

			return col;

			bool CheckCondition(DesignerActionItem ai)
			{
				if (ai.Properties["Condition"] != null)
				{
					var p = actionList.GetType().GetProperty((string)ai.Properties["Condition"], allInstBind, null, typeof(bool), Type.EmptyTypes, null);
					if (p != null)
						return (bool)p.GetValue(actionList, null);
				}
				return true;
			}
		}

		public static IDictionary<string, List<Attribute>> GetRedirectedEvents(this ComponentDesigner d)
		{
			var ret = new Dictionary<string, List<Attribute>>();
			foreach (var evt in d.GetType().GetEvents(allInstBind))
			{
				foreach (var attr in evt.GetCustomAttributes<RedirectedDesignerItemAttribute>(false))
				{
					var attributes = attr.ApplyOtherAttributes
						? evt.GetCustomAttributes<Attribute>().Where(a => !(a is RedirectedDesignerItemAttribute)).ToList()
						: new List<Attribute>();
					ret.Add(evt.Name, attributes);
				}
			}
			return ret.Count > 0 ? ret : null;
		}

		public static void RedirectRegisteredEvents(this ComponentDesigner d, IDictionary properties, IDictionary<string, List<Attribute>> redirectedProps)
		{
			foreach (var propName in redirectedProps.Keys)
			{
				var oldPropertyDescriptor = (PropertyDescriptor)properties[propName];
				if (oldPropertyDescriptor != null)
				{
					var attributes = redirectedProps[propName];
					properties[propName] = TypeDescriptor.CreateProperty(d.GetType(), oldPropertyDescriptor, attributes.ToArray());
				}
			}
		}

		public static IDictionary<string, List<Attribute>> GetRedirectedProperties(this ComponentDesigner d)
		{
			var ret = new Dictionary<string, List<Attribute>>();
			foreach (var prop in d.GetType().GetProperties(allInstBind))
			{
				foreach (var attr in prop.GetCustomAttributes<RedirectedDesignerItemAttribute>(false))
				{
					var attributes = attr.ApplyOtherAttributes
						? prop.GetCustomAttributes<Attribute>().Where(a => !(a is RedirectedDesignerItemAttribute)).ToList()
						: new List<Attribute>();
					ret.Add(prop.Name, attributes);
				}
			}
			return ret.Count > 0 ? ret : null;
		}

		public static void RedirectRegisteredProperties(this ComponentDesigner d, IDictionary properties, IDictionary<string, List<Attribute>> redirectedProps)
		{
			foreach (var propName in redirectedProps.Keys)
			{
				var oldPropertyDescriptor = (PropertyDescriptor)properties[propName];
				if (oldPropertyDescriptor != null)
				{
					var attributes = redirectedProps[propName];
					properties[propName] = TypeDescriptor.CreateProperty(d.GetType(), oldPropertyDescriptor, attributes.ToArray());
				}
			}
		}

		public static void RemoveItems(this ComponentDesigner d, IDictionary values, IEnumerable<string> keysToRemove)
		{
			foreach (var p in keysToRemove)
				if (values.Contains(p))
					values.Remove(p);
		}
	}

	internal class DesignerActionVerbList : DesignerActionList
	{
		private DesignerVerbCollection _verbs;

		public DesignerActionVerbList(DesignerVerbCollection verbs) : base(null)
		{
			_verbs = verbs;
		}

		public override bool AutoShow => false;

		public override DesignerActionItemCollection GetSortedActionItems()
		{
			DesignerActionItemCollection items = new DesignerActionItemCollection();
			foreach (DesignerVerb v in _verbs)
				if ((v.Visible && v.Enabled) && v.Supported)
					items.Add(new DesignerActionVerbItem(v));
			return items;
		}

		public class DesignerActionVerbItem : DesignerActionMethodItem
		{
			private DesignerVerb targetVerb;

			public DesignerActionVerbItem(DesignerVerb verb) : base(null, null, verb.Text, "Verbs", verb.Description, false)
			{
				targetVerb = verb;
			}

			public override void Invoke()
			{
				targetVerb.Invoke();
			}
		}
	}
}